package com.mike.uaa.web.redis;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.mike.uaa.core.Authentication;
import com.mike.uaa.core.DefaultAuthenticationToken;
import com.mike.uaa.jackson.AuthenticationTokenModule;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisOperations;

import java.nio.charset.StandardCharsets;

/**
 * @author z
 */
public class AuthenticationRepositoryImpl implements AuthenticationRepository {

    private static final String AUTHENTICATION_TOKEN_KEY = "security:authentication:token:";

    private final ObjectMapper mapper;
    private final RedisOperations<?, ?> operations;

    public AuthenticationRepositoryImpl(RedisOperations<?, ?> operations) {
        this.operations = operations;
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.registerModules(new AuthenticationTokenModule());
        this.mapper = objectMapper;
    }

    @Override
    public void save(Authentication authentication) {
        String tokenValue = authentication.getTokenValue();
        byte[] rawKey = rawKey(tokenValue);
        byte[] rawValue = toBytes(authentication);
        this.execute((RedisCallback<Void>) connection -> {
            connection.set(rawKey, rawValue);
            long ttl = authentication.getExpiresIn();
            if (ttl > 0) {
                connection.expire(rawKey, ttl);
            }
            return null;
        });
    }

    @Override
    public Authentication findById(String tokenValue) {

        byte[] rawKey = rawKey(tokenValue);

        return this.execute(connection -> {
            byte[] raw = connection.get(rawKey);

            if (raw == null || raw.length == 0) {
                return null;
            }
            return toObject(raw);
        });
    }

    @Override
    public void deleteById(String tokenValue) {
        byte[] rawKey = rawKey(tokenValue);

        this.execute((RedisCallback<Void>) connection -> {
            connection.del(rawKey);
            return null;
        });
    }

    @Override
    public void refresh(Authentication authentication) {
        byte[] rawKey = rawKey(authentication.getTokenValue());
        this.execute((RedisCallback<Void>) connection -> {
            long ttl = authentication.getExpiresIn();
            if (ttl > 0) {
                connection.expire(rawKey, ttl);
            }
            return null;
        });
    }

    @Override
    public <T> T execute(RedisCallback<T> callback) {
        return operations.execute(callback);
    }

    private <T> byte[] toBytes(T source) {
        try {
            if (source instanceof String) {
                return ((String) source).getBytes(StandardCharsets.UTF_8);
            }
            return mapper.writeValueAsBytes(source);
        } catch (Exception e) {
            throw new RuntimeException("Could not serialize: " + e.getMessage(), e);
        }
    }

    private byte[] rawKey(String tokenValue) {
        return (AUTHENTICATION_TOKEN_KEY + tokenValue).getBytes(StandardCharsets.UTF_8);
    }


    private DefaultAuthenticationToken toObject(byte[] source) {
        try {
            if (source == null || source.length == 0) {
                return null;
            }
            return mapper.readValue(source, DefaultAuthenticationToken.class);
        } catch (Exception e) {
            throw new RuntimeException("Could not serialize: " + e.getMessage(), e);
        }
    }

}
